﻿using System;
using System.Collections.Generic;
using System.Text;
using NSoup.Select;
using Com.Toook.AutoDownloadHtml.DownloadHtml.ParseObj;
using NSoup.Nodes;
using Com.Toook.AutoDownloadHtml.Util;
using System.Collections.Specialized;

namespace Com.Toook.AutoDownloadHtml.DownloadHtml.ParseObj
{
    /// <summary>
    /// 根据Step解析出分类页面中详细页面的URL(分类页面url固定，只有一个)
    /// </summary>
    public class GetLinksInKindPage
    {
        private NSoup.Nodes.Document doc = null;
        /// <summary>
        /// 保存分类页面中解析出的所有详细页面Url，防止添加重复的Url
        /// </summary>
        private StringDictionary sdUrlResult = new StringDictionary();
        private int minLinkTextLength = 0;
        /// <summary>
        /// 获取分类中的详细页面URL
        /// </summary>
        public GetLinksInKindPage()
        {
        }
        /// <summary>
        /// 最终解析结果
        /// </summary>
        private List<LeafResult> leafResultList = new List<LeafResult>();
        /// <summary>
        /// 根据分类页面URL和初始步骤，获取第一层 initElements 元素
        /// </summary>
        /// <param name="sourceUrl">分类页面URL</param>
        /// <param name="initStep">初始步骤</param>
        /// <returns></returns>
        public Elements GetInitRootElements(string sourceUrl, Step initStep)
        {
            Elements elements = new Elements();
            try
            {
                doc = NSoup.NSoupClient.Connect(sourceUrl).Get();
            }
            catch (Exception ex)
            {
                LogHelper.Log.Error(" NSoup.NSoupClient.Connect(sourceUrl).Get() 失败。sourceUrl=" + sourceUrl, ex);
                return elements;
            }
            
            Element body = doc.Body;

            elements = GetElements(initStep, body);
            return elements;
        }
        /// <summary>
        /// 获取详细页面内容
        /// </summary>
        /// <param name="ruleList"></param>
        /// <param name="initElements"></param>
        /// <param name="minLinkTextLength"></param>
        /// <returns></returns>
        public List<LeafResult> GetPageDetail(List<Rule> ruleList, Elements initElements, int minLinkTextLength)
        {
            this.minLinkTextLength = minLinkTextLength;
            foreach (Rule rule in ruleList)
            {
                ParseStepByStep(initElements, rule);
            }

            return leafResultList;
        }
        //public LinksInKindPage(string kindPageUrl)
        //{
        //}
        /// <summary>
        /// 获取网站所有分类页面中的详细页面URL
        /// </summary>
        /// <param name="kindList"></param>
        /// <param name="kindsPageUrl">各个分类页面URL</param>
        //private void GetByKindList(List<List<Rule>> kindList, string[] kindsPageUrl)
        //{
        //    foreach (List<Rule> rules in kindList)
        //    {
        //        foreach (Rule rule in rules)
        //        {
        //            while (rule.Next())
        //            {
        //                Elements initElements = GetInitElements();
        //                ParseStepByStep(initElements, rule);
        //            }
        //        }
        //    }
        //}
        /// <summary>
        /// 获取某个分类页面中的详细页面URL
        /// </summary>
        /// <param name="ruleList"></param>
        /// <param name="initElements">第一个Elements元素</param>        
        public List<LeafResult> GetByRuleList(List<Rule> ruleList, Elements initElements, int minLinkTextLength)
        {
            this.minLinkTextLength = minLinkTextLength;
            foreach (Rule rule in ruleList)
            {
                ParseStepByStep(initElements, rule);             
            }

            return leafResultList;
        }
        /// <summary>
        /// 根据配置文件的step、leaf节点解析出详细页面url
        /// </summary>
        /// <param name="initElements"></param>
        /// <param name="rule"></param>
        private void ParseStepByStep(Elements initElements, Rule rule)
        {
            StringBuilder sb = new StringBuilder();

            //i < initElements.Count 主要是判断 GetAndMergeElements 返回的initElements新结果集是否有数据，没有就跳出循环
            for (int i = 0; rule.NextStep() && i < initElements.Count; i++)
            {
                //Elements eles = GetBy(initElements, stepList.currentStep);
                //initElements = eles;

                initElements = GetAndMergeElements(initElements, rule.CurrentStep);
                //GetBy会遍历入参initElements，合并每个节点的结果后返回，将新结果赋给initElements，然后进行下一步操作。
                //stepList.Next()判断是否还有下一步。 
            }

            //Leaf 获取叶子，即详细页面的url / 或者图片、链接等信息块
            for (int i = 0; rule.NextLeaf() && i < initElements.Count; i++)
            {
                initElements = GetAndMergeElements(initElements, rule.CurrentLeaf);                
            }

            if (rule.MultiLeaf == null)
            {
                foreach (Element item in initElements)
                {
                    if (!sdUrlResult.ContainsKey(item.AbsUrl("href")) && item.Text().Length >= this.minLinkTextLength)//标题长度大于N
                    {
                        sdUrlResult.Add(item.AbsUrl("href"), "");
                        LeafResult leafResult = new LeafResult();
                        leafResult.LinkUrl = item.AbsUrl("href");
                        leafResult.LinkText = item.Text();
                        leafResultList.Add(leafResult);
                    }
                }
            }
            else
            {
                foreach (Element item in initElements)
                {
                    ParseMultiLeaf(item, rule);
                }
            }  
        }
        
        private void ParseMultiLeaf(Element leafPartElement, Rule rule)
        {
            rule.MultiLeaf.ResetAll();//重置所有步骤
            LeafResult leafResult = new LeafResult();

            Elements initElements = new Elements();
            initElements.Add(leafPartElement);
            if (rule.MultiLeaf.LinkUrlList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.LinkUrlList, initElements);
                if (text != null && text != "")
                {
                    leafResult.LinkUrl = text;
                    if (!sdUrlResult.ContainsKey(text))
                    {
                        sdUrlResult.Add(text, "");
                    }
                    else
                    {
                        return;//如果url地址重复，返回
                    }
                }
            }
            //else
            //{
            //    LogHelper.Log.Error("");
            //    return;
            //}
            
            if (rule.MultiLeaf.LinkTextList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.LinkTextList, initElements);
                if (text != null && text != "")
                {
                    leafResult.LinkText = text;
                }
            }           
            if(rule.MultiLeaf.ActorList.Count > 0){
                string text = GetLeafText(rule.MultiLeaf.ActorList, initElements);
                if (text != null && text != "")
                {
                    leafResult.Actor = text;
                }
            }

            if (rule.MultiLeaf.AreaList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.AreaList, initElements);
                if (text != null && text != "")
                {
                    leafResult.Area = text;
                }
            }
            if (rule.MultiLeaf.DirectorList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.DirectorList, initElements);
                if (text != null && text != "")
                {
                    leafResult.Director = text;
                }
            }
            if (rule.MultiLeaf.DescriptionList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.DescriptionList, initElements);
                if (text != null && text != "")
                {
                    leafResult.Description = text;
                }
            }
            if (rule.MultiLeaf.ImgTextList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.ImgTextList, initElements);
                if (text != null && text != "")
                {
                    leafResult.ImgText = text;
                }
            }
            if (rule.MultiLeaf.ImgUrlList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.ImgUrlList, initElements);
                if (text != null && text != "")
                {
                    leafResult.ImgUrl = text;
                }
            }
            
            if (rule.MultiLeaf.YearList.Count > 0)
            {
                string text = GetLeafText(rule.MultiLeaf.YearList, initElements);
                if (text != null && text != "")
                {
                    leafResult.Year = text;
                }
            }
            leafResultList.Add(leafResult);
        }
        /// <summary>
        /// 获取某个MultiLeaf叶节点的指定文本
        /// </summary>
        /// <param name="step"></param>
        /// <param name="elements"></param>
        /// <returns>找到叶节点文本返回字符串，如果不是最终文本返回null</returns>
        private string GetLeafText(List<Step> stepList, Elements elements)
        {
            string result = null;
            foreach (Step step in stepList)
            {
                elements = GetAndMergeElements(elements, step);
                if (elements.Count > 0)
                {
                    if (elements[0].TagName() == toookTagName)//如果Tag名称是toook，说明是结果。其他标签需要继续解析
                    {
                        //foreach (Element r in elements)
                        //{
                        //}
                        result = elements[0].Text();//找到toook是最后一步，不用break
                    }//Tag名称不是toook，返回null，外层循环继续查找
                    else if (elements[0].TagName() == toookTagHtmlName)
                    {
                        //foreach (Element r in elements)
                        //{
                        //}
                        result = elements[0].Html();//找到toookHtml
                        break;
                    }
                }
                else
                {
                    result = "";//没有找到正确结果
                    break;
                }
            }
            return result;
        }
        /// <summary>
        /// 遍历elements节点，获取每个节点中符合step条件的子节点集合；合并所有子节点集合，作为最终的results结果集
        /// </summary>
        /// <param name="elements"></param>
        /// <param name="step"></param>
        /// <returns></returns>
        private Elements GetAndMergeElements(Elements elements, Step step)
        {
            Elements results = new Elements();
            
            foreach (Element e in elements)
            {
                Elements eles = GetElements(step, e);
                foreach (Element r in eles)
                {
                    if (r.ChildNodes.Count > 0)
                    {
                        results.Add(r);
                    }
                    else
                    {
                        foreach (string tagName in emptyTags)//如果是空标签，也要加到结果中。空标签ChildNodes.Count为0，
                        {
                            if (tagName == r.TagName())
                            {
                                results.Add(r);
                                break;
                            }
                        }
                    }
                }
            }
            return results;
        }
        private static readonly string[] emptyTags = {
            "meta", "link", "base", "frame", "img", "br", "wbr", "embed", "hr", "input", "keygen", "col", "command", "device"
        };
        /// <summary>
        /// 根据Step获取Elements元素
        /// </summary>
        /// <param name="step"></param>
        /// <param name="element"></param>
        /// <returns></returns>
        private Elements GetElements(Step step, Element element)
        {
            Elements results = new Elements();
            int index;//Elements(index);
            switch (step.GetBy)
            {
                case ByType.Id:
                    Element ele = element.GetElementById(step.AttrName);
                    if (ele != null && ele.ChildNodes.Count > 0)
                    {
                        results.Add(ele);
                    }
                    break;
                case ByType.Tag:
                    results = element.GetElementsByTag(step.AttrName);
                    break;
                case ByType.Class:
                    results = element.GetElementsByClass(step.AttrName);
                    break;
                case ByType.ContainText:
                    results = element.GetElementsContainingText(step.AttrName);
                    break;
                case ByType.ContainOwnText:
                    results = element.GetElementsContainingOwnText(step.AttrName);
                    break;
                case ByType.MatchText:
                    results = element.GetElementsMatchingText(step.AttrName);
                    break;
                case ByType.MatchOwnText:
                    results = element.GetElementsMatchingOwnText(step.AttrName);
                    break;
                case ByType.Attr:
                    results = element.GetElementsByAttribute(step.AttrName);
                    break;
                case ByType.AttrStart:
                    results = element.GetElementsByAttributeStarting(step.AttrName);
                    break;
                case ByType.IndexEquals:
                    int.TryParse(step.AttrName, out index);
                    results = element.GetElementsByIndexEquals(index);
                    break;
                case ByType.IndexLessThan:
                    int.TryParse(step.AttrName, out index);
                    results = element.GetElementsByIndexLessThan(index);
                    break;
                case ByType.IndexGreaterThan:
                    int.TryParse(step.AttrName, out index);
                    results = element.GetElementsByIndexGreaterThan(index);
                    break;
                //属性名，属性值
                case ByType.AttrValue:
                    results = element.GetElementsByAttributeValue(step.AttrName, step.AttrValue);
                    break;
                case ByType.AttrValueStart:
                    results = element.GetElementsByAttributeValueStarting(step.AttrName, step.AttrValue);
                    break;
                case ByType.AttrValueEnd:
                    results = element.GetElementsByAttributeValueEnding(step.AttrName, step.AttrValue);
                    break;
                case ByType.AttrValueContain:
                    results = element.GetElementsByAttributeValueContaining(step.AttrName, step.AttrValue);
                    break;
                case ByType.AttrValueMatch:
                    results = element.GetElementsByAttributeValueMatching(step.AttrName, step.AttrValue);
                    break;
                case ByType.AttrValueContainDateNow:
                    string date = DateTime.Now.ToString(step.AttrValue);//通过step.AttrValue指定的格式，格式化当前日期
                    results = element.GetElementsByAttributeValueContaining(step.AttrName, date);
                    break;
                //获取MultiLeaf的链接、图片、文本等结果
                case ByType.ResultInAttr:
                    string attrResult = element.Attr(step.AttrName);
                    Element p = CreateElementP(attrResult);
                    results.Add(p);
                    break;
                case ByType.ResultInText:
                    string text = element.Text();
                    Element toookText = CreateElementP(text);
                    results.Add(toookText);
                    break;
                case ByType.ResultBetween:
                    string t = StringHelper.GetBetweenStart_FirstEnd(element.Text(), step.AttrName, step.AttrValue);
                    Element subText = CreateElementP(t);
                    results.Add(subText);
                    break;
                case ByType.ResultRemoveBetween:
                    string remove = StringHelper.RemoveBetweenStart_FirstEnd(element.Text(), step.AttrName, step.AttrValue);
                    Element removeEle = CreateElementP(remove);
                    results.Add(removeEle);
                    break;
                case ByType.RemoveTag:
                    Elements tagElements = element.GetElementsByTag(step.AttrName);
                    foreach (Element tag in tagElements)
                    {
                        tag.Remove();
                    }
                    if (step.AttrValue == "isHtmlResult")//如果是结果节点
                    {
                        results.Add(CreateElementHtml(element));
                    }
                    else
                    {
                        results.Add(element);
                    }
                    break;
                default:
                    LogHelper.Log.Error("GetElements(Step step, Element element) GetBy=" + step.GetBy + "不合法。");
                    break;
            }

            return results;
        }
        /// <summary>
        /// 将MultiLeaf的链接、图片、文本等结果保存在&lt;toook&gt;标签中
        /// </summary>
        /// <param name="resultText"></param>
        /// <returns></returns>
        private Element CreateElementP(string resultText)
        {
            NSoup.Parse.Tag tag = NSoup.Parse.Tag.ValueOf(toookTagName);
            Element toook = new Element(tag, "");
            toook.Text(resultText);

            return toook;
        }
        private Element CreateElementHtml(Element element)
        {
            NSoup.Parse.Tag tag = NSoup.Parse.Tag.ValueOf(toookTagHtmlName);
            Element toook = new Element(tag, "");
            toook.Html(element.Html());

            return toook;
        }
        private static readonly string toookTagName = "toook";
        private static readonly string toookTagHtmlName = "toookhtml";//不能用大写，NSoup把tag名称都转换为小写
    }
}
